package com.wang.springbootinit.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wang.springbootinit.common.ErrorCode;
import com.wang.springbootinit.constant.BISystemEnum;
import com.wang.springbootinit.constant.UserConstant;
import com.wang.springbootinit.exception.BusinessException;
import com.wang.springbootinit.manager.RedisLimiterManager;
import com.wang.springbootinit.model.entity.Integral;
import com.wang.springbootinit.model.entity.User;
import com.wang.springbootinit.model.param.IntegralOperateParam;
import com.wang.springbootinit.service.IntegralService;
import com.wang.springbootinit.mapper.IntegralMapper;
import com.wang.springbootinit.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
* @author wang3
* @description 针对表【integral】的数据库操作Service实现
* @createDate 2024-03-16 14:15:56
*/
@Service
@Slf4j
public class IntegralServiceImpl extends ServiceImpl<IntegralMapper, Integral>
    implements IntegralService{
    private static final Logger LOGGER = LoggerFactory.getLogger(IntegralServiceImpl.class);
    @Resource
    private UserService userService;
    @Resource
    private RedisLimiterManager redisLimiterManager;
    @Autowired
    private StringRedisTemplate redisTemplate;

    private static String yyyy_MM_dd = "yyyy-MM-dd";


    /**
     * 根据用户id查看积分详情
     * @param userId
     * @return
     */
    @Override
    public Integral getByUserId(Long userId) {
        if(ObjectUtil.isNull(userId)){
            return null;
        }
        try {
            QueryWrapper<Integral> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("userId",userId);
            return this.getOne(queryWrapper);
        } catch (Exception e) {
            LOGGER.error("getByUserId exception:",e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 签到
     * @param request
     * @return
     */
    @Override
    public Boolean signIn(HttpServletRequest request) {
        try {
            User user = userService.getUser(request);
            if (user == null) {
                throw new BusinessException(BISystemEnum.NOLOGIN.getCode(), BISystemEnum.NOLOGIN.getMsg());
            }
            if(this.isSignIn(user.getId())){
                //已经签到过
                return Boolean.FALSE;
            }
            //对接口进行限流
            redisLimiterManager.doRateLimit(UserConstant.RateLimit + user.getId());

            DateTime currentDateTime = getCurrentDateTime();
            int day = currentDateTime.dayOfMonth();
            // 获取缓存key
            String cacheKey = getCacheKey(user.getId(), currentDateTime);
            // 设置给BitMap对应位标记 其中offset为0表示第一天所以要day-1
            LOGGER.info("signIn redis cacheKey:{}",cacheKey);
            Boolean result = redisTemplate.opsForValue().setBit(cacheKey, day - 1, true);
            // 如果响应true则代表之前已经签到，在Redis指令操作setbit 设置对应位为1的时候，如果之前是0或者不存在会响应0，如果为1则响应1
            if (!result) {
                QueryWrapper<Integral> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("userId",user.getId());
                Integral integral = this.getOne(queryWrapper);
                integral.setIntegral(integral.getIntegral() + UserConstant.signInIntegralCount); //积分+1
                this.updateById(integral);
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        } catch (Exception e) {
            LOGGER.error("IntegralServiceImpl signIn exception:",e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 获取缓存key
     */
    private static String getCacheKey(Long userId, DateTime dateTime) {
        return String.format("USER_SIGN_IN:%s:%s", userId, dateTime.year() + "" + dateTime.monthBaseOne());
    }

    /**
     * 获取当天的时间
     * @return
     */
    private static DateTime getCurrentDateTime() {
        LocalDate date = LocalDate.now();
        // 定义日期格式为 yyyy-MM-dd
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd);
        String formattedDate = date.format(formatter);
        return  DateUtil.parse(formattedDate, yyyy_MM_dd);
    }

    /**
     * 查看用户指定日期是否签到（查看当天是否有签到同理）
     * @param userId
     */
    @Override
    public Boolean isSignIn(Long userId) {
        try {
            DateTime date =   getCurrentDateTime();
            // 获取缓存key
            String cacheKey = getCacheKey(userId, date);
            // 获取日期
            int day = date.dayOfMonth();
            return redisTemplate.opsForValue().getBit(cacheKey, day - 1);
        } catch (Exception e) {
            LOGGER.info("isSignIn exception:",e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 统计用户指定年月签到次数
     * @param userId
     */
    @Override
    public Long getSignInCount(Long userId) {
        DateTime date =   getCurrentDateTime();
        // 获取缓存key
        String cacheKey = getCacheKey(userId, date);
        // 不知道是那个版本才有的下面这个方法，我的现在使用的spring-data-redis是2.3.9.RELEASE 是没有这个方法的，改用connection直接调用bitCount
//        Long count = redisTemplate.opsForValue().bitCount(key, start, end);
        Long count = redisTemplate.execute(connection -> connection.bitCount(cacheKey.getBytes()), true);
        return count;
    }

    /**
     * 获取用户指定年月签到列表，也可以通过这种方式获取用户月签到次数
     *
     * @param userId
     */
    @Override
    public List<Map> getSignInList(Long userId) {
        DateTime date =   getCurrentDateTime();
        // 获取缓存key
        String cacheKey = getCacheKey(userId, date);
        // 获取传入月份有多少天
        YearMonth yearMonth = YearMonth.of(date.year(), date.monthBaseOne());
        int days = yearMonth.lengthOfMonth();
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(days)).valueAt(0);
        // 获取位图的无符号十进制整数
        List<Long> list = redisTemplate.opsForValue().bitField(cacheKey, bitFieldSubCommands);
        if (list == null || list.isEmpty()) {
            return null;
        }
        // 获取位图的无符号十进制整数值
        long bitMapNum = list.get(0);
        // 进行位运算判断组装那些日期有签到
        List<Map> result = new ArrayList<>();
        for (int i = days; i > 0; i--) {
            Map<String, Object> map = new HashMap<>();
            map.put("day", i);
            //先 右移，然后在 左移，如果得到的结果仍然与本身相等，则 最低位是0 所以是未签到
            if (bitMapNum >> 1 << 1 == bitMapNum) {
                map.put("active", false);
            } else {
                //与本身不等，则最低位是1 表示已签到
                map.put("active", true);
            }
            result.add(map);
            // 将位图的无符号十进制整数右移一位，准备下一轮判断
            bitMapNum >>= 1;
        }
        Collections.reverse(result);
        return result;
    }


    /**
     * 兑换调用次数  两积分兑换一次调用次数 [暂时由前端控制]
     * @param param
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor =  Exception.class)
    public Boolean exchange(IntegralOperateParam param, HttpServletRequest request) {
        log.info("IntegralServiceImpl exchange param:{}", JSONUtil.toJsonStr(param));
        if (ObjectUtil.isNull(param.getExchangeIntegralCount()) || ObjectUtil.isNull(param.getExchangeInvokeCount())
                || param.getExchangeIntegralCount() < 0 || param.getExchangeIntegralCount() > 10000) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        try {
            User  user = userService.getUser(request);
            if (user == null) {
                throw new BusinessException(BISystemEnum.NOLOGIN.getCode(), BISystemEnum.NOLOGIN.getMsg());
            }
            //对接口进行限流
            redisLimiterManager.doRateLimit(UserConstant.RateLimit + user.getId());

            QueryWrapper<Integral> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("userId",user.getId());
            Integral integral = this.getOne(queryWrapper);
            if(integral.getIntegral() < param.getExchangeIntegralCount()){
                throw new BusinessException(ErrorCode.NOINTEGRAL); //积分不足
            }
            //添加调用次数
            int invokeCount = user.getInvokeCount() + param.getExchangeInvokeCount();
            UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
            userUpdateWrapper.eq("id",user.getId());
            userUpdateWrapper.set("invokeCount",invokeCount);
            userService.update(userUpdateWrapper);
            //扣除积分
            integral.setIntegral(integral.getIntegral() - param.getExchangeIntegralCount());
            this.updateById(integral);
            log.info("IntegralServiceImpl exchange 兑换成功");
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("IntegralServiceImpl exchange exception:",e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
    }
}




